/******************************************************************************
 *
 *  Copyright (c) 2013, The Linux Foundation. All rights reserved.
 *  Not a Contribution.
 *  Copyright (C) 2005-2012 Broadcom Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *  This file contains the HID host main functions and state machine.
 *
 ******************************************************************************/

#include "bt_target.h"

#if defined(BTA_HD_INCLUDED) && (BTA_HD_INCLUDED == TRUE)

#include <string.h>

#include "bta_hd_api.h"
#include "bta_hd_int.h"
#include "gki.h"

/*****************************************************************************
** Constants and types
*****************************************************************************/

/* state machine states */
enum
{
    BTA_HD_INIT_ST,
    BTA_HD_IDLE_ST,               /* not connected, waiting for connection */
    BTA_HD_CONN_ST,               /* host connected */
    BTA_HD_TRANSIENT_TO_INIT_ST,  /* transient state: going back from CONN to INIT */
};
typedef UINT8 tBTA_HD_STATE;

/* state machine actions */
enum
{
    BTA_HD_REGISTER_ACT,
    BTA_HD_UNREGISTER_ACT,
    BTA_HD_UNREGISTER2_ACT,
    BTA_HD_CONNECT_ACT,
    BTA_HD_DISCONNECT_ACT,
    BTA_HD_ADD_DEVICE_ACT,
    BTA_HD_REMOVE_DEVICE_ACT,
    BTA_HD_SEND_REPORT_ACT,
    BTA_HD_REPORT_ERROR_ACT,
    BTA_HD_VC_UNPLUG_ACT,

    BTA_HD_OPEN_ACT,
    BTA_HD_CLOSE_ACT,
    BTA_HD_INTR_DATA_ACT,
    BTA_HD_GET_REPORT_ACT,
    BTA_HD_SET_REPORT_ACT,
    BTA_HD_SET_PROTOCOL_ACT,
    BTA_HD_VC_UNPLUG_DONE_ACT,
    BTA_HD_SUSPEND_ACT,
    BTA_HD_EXIT_SUSPEND_ACT,

    BTA_HD_NUM_ACTIONS
};

#define BTA_HD_IGNORE BTA_HD_NUM_ACTIONS

typedef void (*tBTA_HD_ACTION)(tBTA_HD_DATA *p_data);

/* action functions */
const tBTA_HD_ACTION bta_hd_action[] =
{
    bta_hd_register_act,
    bta_hd_unregister_act,
    bta_hd_unregister2_act,
    bta_hd_connect_act,
    bta_hd_disconnect_act,
    bta_hd_add_device_act,
    bta_hd_remove_device_act,
    bta_hd_send_report_act,
    bta_hd_report_error_act,
    bta_hd_vc_unplug_act,

    bta_hd_open_act,
    bta_hd_close_act,
    bta_hd_intr_data_act,
    bta_hd_get_report_act,
    bta_hd_set_report_act,
    bta_hd_set_protocol_act,
    bta_hd_vc_unplug_done_act,
    bta_hd_suspend_act,
    bta_hd_exit_suspend_act,
};

/* state table information */
#define BTA_HD_ACTION      0  /* position of action */
#define BTA_HD_NEXT_STATE  1  /* position of next state */
#define BTA_HD_NUM_COLS    2  /* number of columns */

const UINT8 bta_hd_st_init[][BTA_HD_NUM_COLS] =
{
/* Event                               Action                     Next state */
/* BTA_HD_API_REGISTER_APP_EVT   */  { BTA_HD_REGISTER_ACT,       BTA_HD_IDLE_ST },
/* BTA_HD_API_UNREGISTER_APP_EVT */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_API_CONNECT_EVT        */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_API_DISCONNECT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_API_ADD_DEVICE_EVT     */  { BTA_HD_ADD_DEVICE_ACT,     BTA_HD_INIT_ST },
/* BTA_HD_API_REMOVE_DEVICE_EVT  */  { BTA_HD_REMOVE_DEVICE_ACT,  BTA_HD_INIT_ST },
/* BTA_HD_API_SEND_REPORT_EVT    */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_API_REPORT_ERROR_EVT   */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_API_VC_UNPLUG_EVT      */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_OPEN_EVT           */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_CLOSE_EVT          */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_INTR_DATA_EVT      */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_GET_REPORT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_SET_REPORT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_SET_PROTOCOL_EVT   */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_VC_UNPLUG_EVT      */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_SUSPEND_EVT        */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
/* BTA_HD_INT_EXIT_SUSPEND_EVT   */  { BTA_HD_IGNORE,             BTA_HD_INIT_ST },
};

const UINT8 bta_hd_st_idle[][BTA_HD_NUM_COLS] =
{
/* Event                               Action                     Next state */
/* BTA_HD_API_REGISTER_APP_EVT   */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_API_UNREGISTER_APP_EVT */  { BTA_HD_UNREGISTER_ACT,     BTA_HD_INIT_ST },
/* BTA_HD_API_CONNECT_EVT        */  { BTA_HD_CONNECT_ACT,        BTA_HD_IDLE_ST },
/* BTA_HD_API_DISCONNECT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_API_ADD_DEVICE_EVT     */  { BTA_HD_ADD_DEVICE_ACT,     BTA_HD_IDLE_ST },
/* BTA_HD_API_REMOVE_DEVICE_EVT  */  { BTA_HD_REMOVE_DEVICE_ACT,  BTA_HD_IDLE_ST },
/* BTA_HD_API_SEND_REPORT_EVT    */  { BTA_HD_SEND_REPORT_ACT,    BTA_HD_IDLE_ST },
/* BTA_HD_API_REPORT_ERROR_EVT   */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_API_VC_UNPLUG_EVT      */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_INT_OPEN_EVT           */  { BTA_HD_OPEN_ACT,           BTA_HD_CONN_ST },
/* BTA_HD_INT_CLOSE_EVT          */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_INT_INTR_DATA_EVT      */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_INT_GET_REPORT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_INT_SET_REPORT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_INT_SET_PROTOCOL_EVT   */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_INT_VC_UNPLUG_EVT      */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_INT_SUSPEND_EVT        */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
/* BTA_HD_INT_EXIT_SUSPEND_EVT   */  { BTA_HD_IGNORE,             BTA_HD_IDLE_ST },
};

const UINT8 bta_hd_st_conn[][BTA_HD_NUM_COLS] =
{
/* Event                               Action                     Next state */
/* BTA_HD_API_REGISTER_APP_EVT   */  { BTA_HD_IGNORE,             BTA_HD_CONN_ST },
/* BTA_HD_API_UNREGISTER_APP_EVT */  { BTA_HD_DISCONNECT_ACT,     BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_CONNECT_EVT        */  { BTA_HD_IGNORE,             BTA_HD_CONN_ST },
/* BTA_HD_API_DISCONNECT_EVT     */  { BTA_HD_DISCONNECT_ACT,     BTA_HD_CONN_ST },
/* BTA_HD_API_ADD_DEVICE_EVT     */  { BTA_HD_ADD_DEVICE_ACT,     BTA_HD_CONN_ST },
/* BTA_HD_API_REMOVE_DEVICE_EVT  */  { BTA_HD_REMOVE_DEVICE_ACT,  BTA_HD_CONN_ST },
/* BTA_HD_API_SEND_REPORT_EVT    */  { BTA_HD_SEND_REPORT_ACT,    BTA_HD_CONN_ST },
/* BTA_HD_API_REPORT_ERROR_EVT   */  { BTA_HD_REPORT_ERROR_ACT,   BTA_HD_CONN_ST },
/* BTA_HD_API_VC_UNPLUG_EVT      */  { BTA_HD_VC_UNPLUG_ACT,      BTA_HD_CONN_ST },
/* BTA_HD_INT_OPEN_EVT           */  { BTA_HD_IGNORE,             BTA_HD_CONN_ST },
/* BTA_HD_INT_CLOSE_EVT          */  { BTA_HD_CLOSE_ACT,          BTA_HD_IDLE_ST },
/* BTA_HD_INT_INTR_DATA_EVT      */  { BTA_HD_INTR_DATA_ACT,      BTA_HD_CONN_ST },
/* BTA_HD_INT_GET_REPORT_EVT     */  { BTA_HD_GET_REPORT_ACT,     BTA_HD_CONN_ST },
/* BTA_HD_INT_SET_REPORT_EVT     */  { BTA_HD_SET_REPORT_ACT,     BTA_HD_CONN_ST },
/* BTA_HD_INT_SET_PROTOCOL_EVT   */  { BTA_HD_SET_PROTOCOL_ACT,   BTA_HD_CONN_ST },
/* BTA_HD_INT_VC_UNPLUG_EVT      */  { BTA_HD_VC_UNPLUG_DONE_ACT, BTA_HD_IDLE_ST },
/* BTA_HD_INT_SUSPEND_EVT        */  { BTA_HD_SUSPEND_ACT,        BTA_HD_CONN_ST },
/* BTA_HD_INT_EXIT_SUSPEND_EVT   */  { BTA_HD_EXIT_SUSPEND_ACT,   BTA_HD_CONN_ST },
};

const UINT8 bta_hd_st_transient_to_init[][BTA_HD_NUM_COLS] =
{
/* Event                               Action                     Next state */
/* BTA_HD_API_REGISTER_APP_EVT   */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_UNREGISTER_APP_EVT */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_CONNECT_EVT        */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_DISCONNECT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_ADD_DEVICE_EVT     */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_REMOVE_DEVICE_EVT  */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_SEND_REPORT_EVT    */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_REPORT_ERROR_EVT   */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_API_VC_UNPLUG_EVT      */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_INT_OPEN_EVT           */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_INT_CLOSE_EVT          */  { BTA_HD_UNREGISTER2_ACT,    BTA_HD_INIT_ST },
/* BTA_HD_INT_INTR_DATA_EVT      */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_INT_GET_REPORT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_INT_SET_REPORT_EVT     */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_INT_SET_PROTOCOL_EVT   */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_INT_VC_UNPLUG_EVT      */  { BTA_HD_UNREGISTER2_ACT,    BTA_HD_INIT_ST },
/* BTA_HD_INT_SUSPEND_EVT        */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
/* BTA_HD_INT_EXIT_SUSPEND_EVT   */  { BTA_HD_IGNORE,             BTA_HD_TRANSIENT_TO_INIT_ST },
};

/* type for state table */
typedef const UINT8 (*tBTA_HD_ST_TBL)[BTA_HD_NUM_COLS];

/* state table */
const tBTA_HD_ST_TBL bta_hd_st_tbl[] =
{
    bta_hd_st_init,
    bta_hd_st_idle,
    bta_hd_st_conn,
    bta_hd_st_transient_to_init
};


/*****************************************************************************
** Global data
*****************************************************************************/
#if BTA_DYNAMIC_MEMORY == FALSE
tBTA_HD_CB  bta_hd_cb;
#endif

static char *bta_hd_evt_code(tBTA_HD_INT_EVT evt_code);
static char *bta_hd_state_code(tBTA_HD_STATE state_code);

/*******************************************************************************
**
** Function         bta_hd_sm_execute
**
** Description      State machine event handling function for HID Device
**
** Returns          void
**
*******************************************************************************/
void bta_hd_sm_execute(UINT16 event, tBTA_HD_DATA * p_data)
{
    tBTA_HD_ST_TBL  state_table;
    tBTA_HD_STATE   prev_state;
    UINT8           action;
    tBTA_HD         cback_data;
    tBTA_HD_EVT     cback_event = 0;

    APPL_TRACE_EVENT("%s: state=%s (%d) event=%s (%d)", __FUNCTION__,
        bta_hd_state_code(bta_hd_cb.state), bta_hd_cb.state,
        bta_hd_evt_code(event), event);

    prev_state = bta_hd_cb.state;

    memset(&cback_data, 0, sizeof(tBTA_HD));

    state_table = bta_hd_st_tbl[bta_hd_cb.state];

    event &= 0xff;

    if ((action = state_table[event][BTA_HD_ACTION]) < BTA_HD_IGNORE)
    {
        (*bta_hd_action[action])(p_data);
    }

    bta_hd_cb.state = state_table[event][BTA_HD_NEXT_STATE] ;

    if (bta_hd_cb.state != prev_state)
    {
        APPL_TRACE_EVENT("%s: [new] state=%s (%d)", __FUNCTION__,
            bta_hd_state_code(bta_hd_cb.state), bta_hd_cb.state);
    }

    return;
}

/*******************************************************************************
**
** Function         bta_hd_hdl_event
**
** Description      HID device main event handling function.
**
** Returns          void
**
*******************************************************************************/
BOOLEAN bta_hd_hdl_event(BT_HDR *p_msg)
{
    tBTA_HD_CBACK_DATA  *p_data = (tBTA_HD_CBACK_DATA *) p_msg;

    APPL_TRACE_API("%s: p_msg->event=%d", __FUNCTION__, p_msg->event);

    switch (p_msg->event)
    {
        case BTA_HD_API_ENABLE_EVT:
            bta_hd_api_enable((tBTA_HD_DATA *) p_msg);
            break;

        case BTA_HD_API_DISABLE_EVT:
            if (bta_hd_cb.state == BTA_HD_CONN_ST)
            {
                APPL_TRACE_WARNING("%s: host connected, need to "
                    "disconnect before disabling", __FUNCTION__);

                // unregister (and disconnect)
                bta_hd_cb.disable_w4_close = TRUE;
                bta_hd_sm_execute(BTA_HD_API_UNREGISTER_APP_EVT,
                    (tBTA_HD_DATA *) p_msg);
            }
            else
            {
                bta_hd_api_disable();
            }
            break;

        default:
            bta_hd_sm_execute(p_msg->event, (tBTA_HD_DATA *) p_msg);
    }
    return (TRUE);
}

static char *bta_hd_evt_code(tBTA_HD_INT_EVT evt_code)
{
    switch (evt_code)
    {
    case BTA_HD_API_REGISTER_APP_EVT:
        return "BTA_HD_API_REGISTER_APP_EVT";
    case BTA_HD_API_UNREGISTER_APP_EVT:
        return "BTA_HD_API_UNREGISTER_APP_EVT";
    case BTA_HD_API_CONNECT_EVT:
        return "BTA_HD_API_CONNECT_EVT";
    case BTA_HD_API_DISCONNECT_EVT:
        return "BTA_HD_API_DISCONNECT_EVT";
    case BTA_HD_API_ADD_DEVICE_EVT:
        return "BTA_HD_API_ADD_DEVICE_EVT";
    case BTA_HD_API_REMOVE_DEVICE_EVT:
        return "BTA_HD_API_REMOVE_DEVICE_EVT";
    case BTA_HD_API_SEND_REPORT_EVT:
        return "BTA_HD_API_SEND_REPORT_EVT";
    case BTA_HD_API_REPORT_ERROR_EVT:
        return "BTA_HD_API_REPORT_ERROR_EVT";
    case BTA_HD_API_VC_UNPLUG_EVT:
        return "BTA_HD_API_VC_UNPLUG_EVT";
    case BTA_HD_INT_OPEN_EVT:
        return "BTA_HD_INT_OPEN_EVT";
    case BTA_HD_INT_CLOSE_EVT:
        return "BTA_HD_INT_CLOSE_EVT";
    case BTA_HD_INT_INTR_DATA_EVT:
        return "BTA_HD_INT_INTR_DATA_EVT";
    case BTA_HD_INT_GET_REPORT_EVT:
        return "BTA_HD_INT_GET_REPORT_EVT";
    case BTA_HD_INT_SET_REPORT_EVT:
        return "BTA_HD_INT_SET_REPORT_EVT";
    case BTA_HD_INT_SET_PROTOCOL_EVT:
        return "BTA_HD_INT_SET_PROTOCOL_EVT";
    case BTA_HD_INT_VC_UNPLUG_EVT:
        return "BTA_HD_INT_VC_UNPLUG_EVT";
    case BTA_HD_INT_SUSPEND_EVT:
        return "BTA_HD_INT_SUSPEND_EVT";
    case BTA_HD_INT_EXIT_SUSPEND_EVT:
        return "BTA_HD_INT_EXIT_SUSPEND_EVT";
    default:
        return "<unknown>";
    }
}

static char *bta_hd_state_code(tBTA_HD_STATE state_code)
{
    switch (state_code)
    {
    case BTA_HD_INIT_ST:
        return "BTA_HD_INIT_ST";
    case BTA_HD_IDLE_ST:
        return "BTA_HD_IDLE_ST";
    case BTA_HD_CONN_ST:
        return "BTA_HD_CONN_ST";
    case BTA_HD_TRANSIENT_TO_INIT_ST:
        return "BTA_HD_TRANSIENT_TO_INIT_ST";
    default:
        return "<unknown>";
    }
}

#endif /* BTA_HD_INCLUDED */
